// +----------------------------------------------------------------------
// | Manong.Cloud [ 领酷码农云 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2020 http://www.manong.cloud All rights reserved.
// +----------------------------------------------------------------------
// | Author: 稻草人 <qinuoyun@qq.com>
// +----------------------------------------------------------------------
// | Member: 围棋 飘逸者 Loumt Silence24
// +----------------------------------------------------------------------
import fs from "fs";
import path from "path";
import crypto from "crypto";
import zlib from "zlib";


/**
 * 获取环境变量
 * @return {[type]}     [description]
 * @param envName
 * @param value
 */
global.getEnv = function(envName, value) {
    if (process.env[envName]) {
        return process.env[envName];
    } else {
        return value;
    }
};

/**
 * 把字符串打散为数组
 * @param  {[type]} obj [description]
 * @return {[type]}     [description]
 */
global.P = function(array) {
    console.log("数据打印", array);
};


/**
 * 解压base64数据
 */
global.unzipBase64 = function(content) {
    return new Promise(async (resolve, reject) => {
        if (content) {
            let dataBuffer = new Buffer.from(content, "base64");
            zlib.unzip(dataBuffer, async (error, buffer) => {
                if (error) {
                    reject(error);
                } else {
                    let data = JSON.parse(buffer.toString());
                    resolve(data);
                }
            });
        } else {
            resolve("");
        }
    });
};

/**
 * 代码解压缩
 */
global.codeUnzip = function(base64) {
    return new Promise((resolve, reject) => {
        try {
            var dataBuffer = new Buffer.from(base64, "base64");
            //对buffer数据进行解压
            zlib.unzip(dataBuffer, function(err, buffer) {
                resolve(buffer.toString());
            });
        } catch (error) {
            reject(error);
        }
    });
};

/**
 * 大M方法加载模型
 * @param  {[type]} obj [description]
 * @return {[type]}     [description]
 */
global.M = function(name, ctx = "") {
    try {
        if (name) {
            let Model = "";
            let Value = "";
            if (!strstr(name, ".")) {
                Model = name;
                Value = name;
            } else {
                //处理数据值
                let name_array = explode(".", name);
                Model = name_array[0];
                Value = name_array[1];
            }
            //获取模型数据
            const ModelData = _getModelFile(Model, Value);
            return new ModelData(ctx);
        } else {
            throw new Error("模型名称不能为空");
        }
    } catch (error) {
        throw error;
    }
};

/**
 * 获取模型文件
 * @return {[type]} [description]
 */
const _getModelFile = function(Model, Value) {
    const model_file = ROOT_DIR + DS + "src" + DS + "modules" + DS + ucfirst(Model) + DS + ucfirst(Value) + ".js";
    let modelName = ucfirst(Model) + "." + ucfirst(Value);
    if (__modules__[modelName]) {
        return __modules__[modelName];
    }
    throw new Error("【M】模型文件" + model_file + "不存在,请检查模型目录");
};


/**
 * 把字符串打散为数组
 * @param  {[type]} obj [description]
 * @return {[type]}     [description]
 */
global.explode = function(str, array) {
    return array.slice(0).split(str);
};

/**
 * md5加密
 * @param  {[type]} str [description]
 * @return {[type]}     [description]
 */
global.md5 = function(str) {
    var crypto_md5 = crypto.createHash("md5");
    crypto_md5.update(str, "utf8"); // 加入编码
    return crypto_md5.digest("hex");
};

/**
 * 数组转字符串
 * @param  {[type]} obj [description]
 * @return {[type]}     [description]
 */
global.implode = function(value, data) {
    return data.join(value);
};

/**
 * 判断字段是否设置
 * @param  {[type]} obj [description]
 * @return {[type]}     [description]
 */
global.isset = function(value) {
    return typeof value != "undefined" ? true : false;
};

/**
 * 判断文件是否存在
 * @param  {[type]}  dirname [description]
 * @return {Boolean}         [description]
 */
global.is_file = function(dirname) {
    try {
        fs.statSync(path.join(dirname));
        return true;
    } catch (e) {
        return false;
    }
};

/**
 * 下划线转换驼峰
 * @param name
 * @returns {*}
 */
global.to_hump = function(name) {
    return name.replace(/\_(\w)/g, function(all, letter) {
        return letter.toUpperCase();
    });
};

/**
 * 获取模型名称
 * @param name
 */
global.get_model_name = function(name) {
    let _arr = explode("_", name);
    if (_arr.length > 1) {
        return ucfirst(_arr[0]) + "." + ucfirst(to_hump(name));
    } else {
        return ucfirst(name);
    }
};

/**
 * 驼峰转换下划线
 * @param name
 * @returns {string}
 */
global.to_line = function(name) {
    return name.replace(/([A-Z])/g, "_$1").toLowerCase();
};

/**
 * 查找函数
 * @param  {[type]} list [description]
 * @param  {[type]} f    [description]
 * @return {[type]}      [description]
 */
global.find = function(list, f) {
    return list.filter(f)[0];
};

/**
 *考虑到圆形结构，深度复制给定对象。
 *这个函数缓存所有嵌套的对象及其副本。
 *如果检测到循环结构，使用缓存副本避免无限循环。
 * @param  {[type]} obj   [description]
 * @param  {Array}  cache [description]
 * @return {[type]}       [description]
 */
global.copy_data = function(obj, cache = []) {
    // 如果obj是不可变值，就返回
    if (obj === null || typeof obj !== "object") {
        return obj;
    }

    // 如果obj被击中，则为圆形结构
    const hit = find(cache, c => c.original === obj);
    if (hit) {
        return hit.copy;
    }

    const copy = Array.isArray(obj) ? [] : {};
    //先把副本放到缓存里
    //因为我们想在copy_data递归中引用它
    cache.push({
        original: obj,
        copy
    });

    Object.keys(obj).forEach(key => {
        copy[key] = copy_data(obj[key], cache);
    });

    return copy;
};


/**
 * 首字母转大写
 * @param  {[type]} value [description]
 * @return {[type]}       [description]
 */
global.ucfirst = function(value) {
    return value.replace(/^\S/, s => s.toUpperCase());
};

/**
 * 字符串查找
 * @param  {[type]} $haystack [description]
 * @param  {[type]} $needle   [description]
 * @return {[type]}           [description]
 */
global.strstr = function(haystack, needle) {
    if (haystack.indexOf(needle) !== -1) {
        return true;
    } else {
        return false;
    }
};

/**
 * 判断内容是否为空
 * @param  {[type]} value [description]
 * @return {[type]}       [description]
 */
global.empty = function(value) {
    switch (typeof value) {
        case "string":
            if (value == null || value == "") {
                return true;
            } else {
                return false;
            }
            break;
        case "object":
            if (value === null || Object.keys(value).length === 0) {
                return true;
            } else {
                return false;
            }
            break;
        case "function":
            return false;
            break;
        case "boolean":
            return !value;
            break;
        case "number":
            if (value === NaN) {
                return true;
            } else {
                return false;
            }
            break;
        case "null":
        case "undefined":
        default:
            return true;
    }
    if ((value.length === 0 || value === null || value === "" || value === undefined || value === false) || Object.keys(value).length === 0) {
        return false;
    } else {
        return true;
    }
};


/**
 * 判断是不是数组
 * @param  {[type]} value [description]
 * @param  {[type]} array [description]
 * @return {[type]}       [description]
 */
global.in_array = function(value, array) {
    if (array.indexOf(value) == -1) {
        return false;
    }
    return true;
};

/**
 * 判断是否为对象
 * @param  {[type]}  data [description]
 * @return {Boolean}      [description]
 */
global.is_object = function(data) {
    const object = Object.prototype.toString;
    if (object.call(data) == "[object Object]") {
        return true;
    } else {
        return false;
    }
};


/**
 * 判断是否为字符串
 * @return {Boolean} [description]
 */
global.is_string = function(data) {
    const object = Object.prototype.toString;
    if (object.call(data) == "[object String]") {
        return true;
    } else {
        return false;
    }
};

/**
 * 判断是否为数组
 * @param  {[type]}  value [description]
 * @return {Boolean}       [description]
 */
global.is_array = function(value) {
    if (typeof Array.isArray === "function") {
        if (Array.isArray(value)) {
            return true;
        }
    }
    if (Object.prototype.toString.call(value) === "[object Array]") {
        return true;
    }
    if (Object.prototype.toString.call(value) === "[object Object]") {
        return true;
    }
    return false;
};

/**
 * 合并对象
 * @param  {[type]} target [description]
 * @param  {[type]} source [description]
 * @return {[type]}        [description]
 */
global.extend = function(target, ...arg) {
    return arg.reduce((acc, cur) => {
        return Object.keys(cur).reduce((subAcc, key) => {
            const srcVal = cur[key];
            if (is_object(srcVal)) {
                subAcc[key] = extend(subAcc[key] ? subAcc[key] : {}, srcVal);
            } else if (is_array(srcVal)) {
                // series: []，下层数组直接赋值
                subAcc[key] = srcVal.map((item, idx) => {
                    if (is_object(item)) {
                        const curAccVal = subAcc[key] ? subAcc[key] : [];
                        return extend(curAccVal[idx] ? curAccVal[idx] : {}, item);
                    } else {
                        return item;
                    }
                });
            } else {
                subAcc[key] = srcVal;
            }
            return subAcc;
        }, acc);
    }, target);
};


/**
 * 判断是否为null
 * @param  {[type]}  data [description]
 * @return {Boolean}      [description]
 */
global.is_null = function(data) {
    const object = Object.prototype.toString;
    if (object.call(data) == "[object Null]") {
        return true;
    } else {
        return false;
    }
};


/**
 * 数组统计
 * @param  {[type]} value [description]
 * @return {[type]}       [description]
 */
global.count = function(value) {
    if (is_object(value)) {
        return Object.keys(value).length;
    } else {
        return value.length;
    }

};

/**
 * 获取对象最后一个键
 * @param  {[type]} data [description]
 * @return {[type]}      [description]
 */
global.end = function(data) {
    if (is_object(data)) {
        let _end = Object.keys(data).length - 1;
        return Object.keys(data)[_end];
    } else if (is_array(data)) {
        let _end = data.length - 1;
        return data[_end];
    } else {
        return null;
    }
};


/**
 * 去除字符串左边
 * @param  {[type]} str   [description]
 * @param  {String} value [description]
 * @return {[type]}       [description]
 */
global.ltrim = function(str, value = " ") {
    let leng = value.length;
    let init = str.slice(0, leng);
    if (init == value) {
        return str.slice(leng);
    } else {
        return str;
    }
};

/**
 * 去除字符串右边
 * @param  {[type]} str   [description]
 * @param  {String} value [description]
 * @return {[type]}       [description]
 */
global.rtrim = function(str, value = " ") {
    let leng = value.length;
    let init = str.substring(str.length - leng);
    if (init == value) {
        return str.slice(0, -leng);
    } else {
        return str;
    }
};


/**
 * 去除字符串两边字符
 * @param  {[type]} str   [description]
 * @param  {[type]} value [description]
 * @return {[type]}       [description]
 */
global.trim = function(str, value = " ") {
    str = ltrim(str, value);
    str = rtrim(str, value);
    return str;
};


global.randomString = function(number = 6, type = "number") {
    let str = "0123456789";
    if (type == "string") {
        str += "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789";
    }
    let len = str.length;
    let ret = "";
    for (var i = 0; i < number; i++) ret += str.charAt(Math.floor(Math.random() * len));
    return ret;
};


/**
 * 获取API文件
 * @return {[type]} [description]
 */
global.getComponent = function(baseName) {
    let baseArray = baseName.split("/");
    const component_file = ROOT_DIR + DS + "src" + DS + baseName + ".js";
    if (baseArray.length >= 2 && baseArray[0] === "controllers") {
        let componentName = baseArray[1];
        if (__controllers__[componentName]) {
            return __controllers__[componentName];
        }
    }
    throw new Error("组建模块" + component_file + "不存在,请检查");
};


/**
 * 转json
 * @param  {[type]} value [description]
 * @return {[type]}       [description]
 */
global.to_json = function(value) {
    return JSON.stringify(value).toString();
};

/**
 * json转数组
 * @param  {[type]} value [description]
 * @return {[type]}       [description]
 */
global.to_array = function(value) {
    let res = JSON.parse(value);
    if (typeof res === "object") {
        return res;
    } else {
        return value;
    }
};

/**
 * 批量正则替换
 * @param  {[type]} requestedUrl [description]
 * @param  {[type]} opts         [description]
 * @return {[type]}              [description]
 */
global.matchesPath = function(requestedUrl, path) {
    let paths = !path || Array.isArray(path) ? path : [path];
    if (paths) {
        return paths.some(function(p) {
            return (typeof p === "string" && p === requestedUrl) ||
                (p instanceof RegExp && !!p.exec(requestedUrl));
        });
    }
    return false;
};


/**
 * 和PHP一样的时间戳格式化函数
 * @param {string} format 格式
 * @param {int} timestamp 要格式化的时间 默认为当前时间
 * @return {string}   格式化的时间字符串
 */
global.date = function(format, timestamp) {
    var a, jsdate = ((timestamp) ? new Date(timestamp * 1000) : new Date());
    var pad = function(n, c) {
        if ((n = n + "").length < c) {
            return new Array(++c - n.length).join("0") + n;
        } else {
            return n;
        }
    };
    var txt_weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    var txt_ordin = { 1: "st", 2: "nd", 3: "rd", 21: "st", 22: "nd", 23: "rd", 31: "st" };
    var txt_months = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    var f = {
        // Day
        d: function() {
            return pad(f.j(), 2);
        },
        D: function() {
            return f.l().substr(0, 3);
        },
        j: function() {
            return jsdate.getDate();
        },
        l: function() {
            return txt_weekdays[f.w()];
        },
        N: function() {
            return f.w() + 1;
        },
        S: function() {
            return txt_ordin[f.j()] ? txt_ordin[f.j()] : "th";
        },
        w: function() {
            return jsdate.getDay();
        },
        z: function() {
            return (jsdate - new Date(jsdate.getFullYear() + "/1/1")) / 864e5 >> 0;
        },

        // Week
        W: function() {
            var a = f.z(),
                b = 364 + f.L() - a;
            var nd2, nd = (new Date(jsdate.getFullYear() + "/1/1").getDay() || 7) - 1;
            if (b <= 2 && ((jsdate.getDay() || 7) - 1) <= 2 - b) {
                return 1;
            } else {
                if (a <= 2 && nd >= 4 && a >= (6 - nd)) {
                    nd2 = new Date(jsdate.getFullYear() - 1 + "/12/31");
                    return date("W", Math.round(nd2.getTime() / 1000));
                } else {
                    return (1 + (nd <= 3 ? ((a + nd) / 7) : (a - (7 - nd)) / 7) >> 0);
                }
            }
        },

        // Month
        F: function() {
            return txt_months[f.n()];
        },
        m: function() {
            return pad(f.n(), 2);
        },
        M: function() {
            return f.F().substr(0, 3);
        },
        n: function() {
            return jsdate.getMonth() + 1;
        },
        t: function() {
            var n;
            if ((n = jsdate.getMonth() + 1) == 2) {
                return 28 + f.L();
            } else {
                if (n & 1 && n < 8 || !(n & 1) && n > 7) {
                    return 31;
                } else {
                    return 30;
                }
            }
        },

        // Year
        L: function() {
            var y = f.Y();
            return (!(y & 3) && (y % 1e2 || !(y % 4e2))) ? 1 : 0;
        },
        //o not supported yet
        Y: function() {
            return jsdate.getFullYear();
        },
        y: function() {
            return (jsdate.getFullYear() + "").slice(2);
        },

        // Time
        a: function() {
            return jsdate.getHours() > 11 ? "pm" : "am";
        },
        A: function() {
            return f.a().toUpperCase();
        },
        B: function() {
            // peter paul koch:
            var off = (jsdate.getTimezoneOffset() + 60) * 60;
            var theSeconds = (jsdate.getHours() * 3600) + (jsdate.getMinutes() * 60) + jsdate.getSeconds() + off;
            var beat = Math.floor(theSeconds / 86.4);
            if (beat > 1000) beat -= 1000;
            if (beat < 0) beat += 1000;
            if ((String(beat)).length == 1) beat = "00" + beat;
            if ((String(beat)).length == 2) beat = "0" + beat;
            return beat;
        },
        g: function() {
            return jsdate.getHours() % 12 || 12;
        },
        G: function() {
            return jsdate.getHours();
        },
        h: function() {
            return pad(f.g(), 2);
        },
        H: function() {
            return pad(jsdate.getHours(), 2);
        },
        i: function() {
            return pad(jsdate.getMinutes(), 2);
        },
        s: function() {
            return pad(jsdate.getSeconds(), 2);
        },
        //u not supported yet

        // Timezone
        //e not supported yet
        //I not supported yet
        O: function() {
            var t = pad(Math.abs(jsdate.getTimezoneOffset() / 60 * 100), 4);
            if (jsdate.getTimezoneOffset() > 0) t = "-" + t;
            else t = "+" + t;
            return t;
        },
        P: function() {
            var O = f.O();
            return (O.substr(0, 3) + ":" + O.substr(3, 2));
        },
        //T not supported yet
        //Z not supported yet

        // Full Date/Time
        c: function() {
            return f.Y() + "-" + f.m() + "-" + f.d() + "T" + f.h() + ":" + f.i() + ":" + f.s() + f.P();
        },
        //r not supported yet
        U: function() {
            return Math.round(jsdate.getTime() / 1000);
        }
    };

    return format.replace(/[\\]?([a-zA-Z])/g, function(t, s) {
        let ret = "";
        if (t != s) {
            // escaped
            ret = s;
        } else if (f[s]) {
            // a date function exists
            ret = f[s]();
        } else {
            // nothing special
            ret = s;
        }
        return ret;
    });
};

/**
 * 加密
 * @param  {[type]} str [description]
 * @param  {String} key [description]
 * @param  {String} iv  [description]
 * @return {[type]}     [description]
 */
global.cipher256 = function(str, key = 'building', iv = 'hk') {
    key = crypto.createHash('sha256').update(String(key)).digest('base64').substr(0, 32);
    iv = crypto.createHash('sha256').update(String(iv)).digest('base64').substr(0, 16);
    const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
    let cip = cipher.update(str, 'utf8', 'hex');
    cip += cipher.final('hex');
    return cip;
}

/**
 * 解密
 * @param  {[type]} str [description]
 * @param  {String} key [description]
 * @param  {String} iv  [description]
 * @return {[type]}     [description]
 */
global.decipher256 = function(str, key = 'building', iv = 'hk') {
    key = crypto.createHash('sha256').update(String(key)).digest('base64').substr(0, 32);
    iv = crypto.createHash('sha256').update(String(iv)).digest('base64').substr(0, 16);
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let dec = decipher.update(str, 'hex', 'utf8');
    dec += decipher.final('utf8');
    return dec;
}

/**
 * 获取路由中的参数
 * @param  {[type]} url [description]
 * @return {[type]}     [description]
 */
global.getParams = function(url) {
    if (url.indexOf('?') != -1) {
        let obj = {}
        let arr = url.slice(url.indexOf('?') + 1).split('&')
        arr.forEach(item => {
            let param = item.split('=')
            obj[param[0]] = param[1]
        })
        return obj;
    } else {
        return {};
    }
};